/*
 *
 * Copyright 2015 gRPC authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

#include "cpp_generator.h"
#include "generator_helpers.h"
#include "crc_hash.h"

#include <google/protobuf/compiler/code_generator.h>
#include <google/protobuf/compiler/cpp/options.h>
#include <google/protobuf/descriptor.h>
#include <google/protobuf/descriptor.pb.h>
#include <google/protobuf/io/printer.h>

#include <map>
#include <sstream>

namespace lrpc_cpp_generator {
namespace {

template <class T>
std::string as_string(T x) {
  std::ostringstream out;
  out << x;
  return out.str();
}

inline bool ClientOnlyStreaming(const google::protobuf::MethodDescriptor* method) {
  return method->client_streaming() && !method->server_streaming();
}

inline bool ServerOnlyStreaming(const google::protobuf::MethodDescriptor* method) {
  return !method->client_streaming() && method->server_streaming();
}

inline bool BidiStreaming(const google::protobuf::MethodDescriptor* method) {
  return method->client_streaming() && method->server_streaming();
}
inline bool NoStreaming(const google::protobuf::MethodDescriptor* method) {
  return !method->client_streaming() && !method->server_streaming();
}

std::string FilenameIdentifier(const std::string& filename) {
  std::string result;
  for (unsigned i = 0; i < filename.size(); i++) {
    char c = filename[i];
    if (isalnum(c)) {
      result.push_back(c);
    } else {
      static char hex[] = "0123456789abcdef";
      result.push_back('_');
      result.push_back(hex[(c >> 4) & 0xf]);
      result.push_back(hex[c & 0xf]);
    }
  }
  return result;
}
}  // namespace

template <class T, size_t N>
T* array_end(T (&array)[N]) {
  return array + N;
}

void PrintIncludes(google::protobuf::io::Printer* printer,
                   const std::vector<std::string>& headers,
                   bool use_system_headers, const std::string& search_path) {
  std::map<std::string, std::string> vars;

  vars["l"] = use_system_headers ? '<' : '"';
  vars["r"] = use_system_headers ? '>' : '"';

  if (!search_path.empty()) {
    vars["l"] += search_path;
    if (search_path[search_path.size() - 1] != '/') {
      vars["l"] += '/';
    }
  }

  for (auto i = headers.begin(); i != headers.end(); i++) {
    vars["h"] = *i;
    printer->Print(vars, "#include $l$$h$$r$\n");
  }
}

std::string GetHeaderPrologue(const google::protobuf::FileDescriptor* file) {
  std::string output;
  {
    // Scope the output stream so it closes and finalizes output to the string.
    google::protobuf::io::StringOutputStream output_stream(&output);   
    google::protobuf::io::Printer printer(&output_stream, '$');

    std::map<std::string, std::string> vars;

    vars["filename"] = file->name();
    vars["filename_identifier"] = FilenameIdentifier(file->name());
    vars["filename_base"] = lrpc_generator::StripProto(file->name()); //file->filename_without_ext();
    vars["message_header_ext"] = ".pb.h";

    printer.Print(vars, "// Generated by the iRPC C++ plugin (based on gRPC - https://github.com/grpc/grpc/tree/master/src/compiler.) \n");
    printer.Print(vars,
                   "// If you make any local change, they will be lost.\n");
    printer.Print(vars, "// source: $filename$\n");
    printer.Print(vars, "#ifndef IRPC_$filename_identifier$__INCLUDED\n");
    printer.Print(vars, "#define IRPC_$filename_identifier$__INCLUDED\n");
    printer.Print(vars, "\n");
    printer.Print(vars, "#include \"$filename_base$$message_header_ext$\"\n");
    printer.Print(vars, "\n");
  }
  return output;
}

// Convert from "a/b/c.proto" to "#include \"a/b/c$message_header_ext$\"\n"
std::string ImportInludeFromProtoName(const std::string& proto_name) {
  return std::string("#include \"") +
         proto_name.substr(0, proto_name.size() - 6) +
         std::string("$message_header_ext$\"\n");
}

std::string GetHeaderIncludes(const google::protobuf::FileDescriptor* file) {
  std::string output;

  {
    // Scope the output stream so it closes and finalizes output to the string.
    google::protobuf::io::StringOutputStream output_stream(&output);   
    //google::protobuf::io::Printer printer(&output_stream, '$'); 
    google::protobuf::io::Printer *printer = new google::protobuf::io::Printer(&output_stream, '$'); 
    std::map<std::string, std::string> vars;

    static const char* headers_strs[] = {
        "lrpc.h"
    };
    std::vector<std::string> headers(headers_strs, array_end(headers_strs));
    PrintIncludes(printer, headers, false, "");  // true for #include <*.h> ; false for #include "*.h"

    printer->Print(vars, "\n");
    printer->Print("using namespace lrpc;\n\n");

    vars["message_header_ext"] = ".pb.h";
 
    if (!file->package().empty()) {
      std::vector<std::string> parts = lrpc_generator::tokenize(file->package(), "."); //file->package_parts();

      for (auto part = parts.begin(); part != parts.end(); part++) {
        vars["part"] = *part;
        printer->Print(vars, "namespace $part$ {\n");
      }
      printer->Print(vars, "\n");
    }
    delete printer; 
  }
  
  return output;
}

void PrintHeaderClientMethod(google::protobuf::io::Printer* printer,
                                       const google::protobuf::MethodDescriptor* method,
                                       std::map<std::string, std::string>* vars,
                                       bool is_public) {

  auto *req = method->input_type();
  auto *resp = method->output_type();

  (*vars)["Method"] = method->name();
  (*vars)["Request"] = req->name(); //method->input_type_name();
  (*vars)["Response"] = resp->name(); //method->output_type_name();

  struct {
    std::string prefix;
    std::string method_params;  // extra arguments to method
    std::string raw_args;       // extra arguments to raw version of method
  } async_prefixes[] = {{"Async", ", void* tag", ", tag"},
                        {"PrepareAsync", "", ""}};

  if (is_public) {
    if (NoStreaming(method)) {
      printer->Print(
          *vars,
          "int32_t $Method$("
          "const $Request$& request, $Response$* response);\n");
      printer->Print(
          *vars,
          "int32_t "
          "Async$Method$("
          "const $Request$& request, "
          "$Response$* response, "
          "Rpc_active_call *new_call); \n");
    } else if (ClientOnlyStreaming(method)) {
    } else if (ServerOnlyStreaming(method)) {
    } else if (BidiStreaming(method)) {
    }
  } else { // private methods used internally
    if (NoStreaming(method)) {
    } else if (ClientOnlyStreaming(method)) {
    } else if (ServerOnlyStreaming(method)) {
    } else if (BidiStreaming(method)) {
    }
  }
}

// not used and maybe useful to have RPC handlers as members in the future
void PrintHeaderClientMethodData(google::protobuf::io::Printer* printer,
                                       const google::protobuf::MethodDescriptor* method,
                                       std::map<std::string, std::string>* vars) {
  (*vars)["Method"] = method->name();
  printer->Print(*vars,
                 "const ::lrpc::RpcMethod rpcmethod_$Method$_;\n");
}

void PrintHeaderServerMethodSync(google::protobuf::io::Printer* printer,
                                       const google::protobuf::MethodDescriptor* method,
                                       std::map<std::string, std::string>* vars) {

  auto *req = method->input_type();
  auto *resp = method->output_type();

  (*vars)["Method"] = method->name();
  (*vars)["Request"] = req->name(); //method->input_type_name();
  (*vars)["Response"] = resp->name(); //method->output_type_name();
  //
  //printer->Print(method->GetLeadingComments("//").c_str());
  if (NoStreaming(method)) {
    printer->Print(*vars,
                   "virtual int32_t $Method$("
                   "const $Request$* request, "
                   "$Response$* response);\n");
  } else if (ClientOnlyStreaming(method)) {
  } else if (ServerOnlyStreaming(method)) {
  } else if (BidiStreaming(method)) {
  }
  //printer->Print(method->GetTrailingComments("//").c_str());
}

void PrintHeaderService(google::protobuf::io::Printer* printer,
                                       const google::protobuf::ServiceDescriptor *service,
                                       std::map<std::string, std::string>* vars) {
  (*vars)["Service"] = service->name();
  //printer->Print(service->GetLeadingComments("//").c_str());
  printer->Print(*vars,
                 "class $Service$ final {\n"
                 " public:\n");
  printer->Indent();

  // Service metadata
  printer->Print(*vars,
                 "static constexpr char const* service_full_name() {\n"
                 "  return \"$Package$$Service$\";\n"
                 "}\n\n");

  // Client side
  printer->Print("//////////////////////////////////////////\n");  
  printer->Print(
      "class Stub  {\n"
      " public:\n");
  printer->Indent();
  printer->Print("Stub(Lrpc_endpoint *lrpc_endpoint, const char *uri); \n"); 
  for (int i = 0; i < service->method_count(); ++i) {
    PrintHeaderClientMethod(printer, service->method(i), vars, true);
  }

  printer->Outdent();
  printer->Print("\n private:\n");
  printer->Indent();
  printer->Print("Lrpc_endpoint *lrpc_endpoint_;\n");

  printer->Outdent();
  printer->Print("};\n\n");

  // Server side - base
  printer->Print("//////////////////////////////////////////\n"); 
  printer->Print(
      "class Service : public lrpc::rpc_service {\n"
      " public:\n");
  printer->Indent();
  printer->Print("Service(Lrpc_endpoint *lrpc_endpoint);\n");
  printer->Print("virtual ~Service();\n\n");

  // generate service_name()
  printer->Print(*vars, "const char *service_name() const { return \"$Service$\"; } \n"); 
  
  // generate service_id()
  std::string temp = service->name();
  (*vars)["Service_ID"] = std::to_string(rte_hash_crc(temp.c_str(), temp.length(), 0)); 
  printer->Print(*vars, "uint32_t service_id() const { return $Service_ID$; } \n"); 

  // generate method_for_request_id()
  printer->Print("virtual RpcServiceMethod * method_for_request_id(uint32_t idx) override final;\n\n");

  // generate methods
  for (int i = 0; i < service->method_count(); ++i) {
    PrintHeaderServerMethodSync(printer, service->method(i), vars);
  }
  printer->Print("\n");

  printer->Outdent();
  printer->Print(" private:\n");
  printer->Indent();
  printer->Print("std::vector<std::unique_ptr<lrpc::RpcServiceMethod>> methods_;\n"); 
  printer->Print("Lrpc_endpoint *lrpc_endpoint_;\n");

  printer->Outdent();
  printer->Print("};\n");

  // closing class RPC final 
  printer->Outdent();
  printer->Print("};\n\n");
}

std::string GetHeaderServices(const google::protobuf::FileDescriptor* file) {
  std::string output;
  {
    // Scope the output stream so it closes and finalizes output to the string.
    //auto printer = file->CreatePrinter(&output);
    google::protobuf::io::StringOutputStream output_stream(&output);   
    //google::protobuf::io::Printer printer(&output_stream, '$'); 
    google::protobuf::io::Printer *printer = new google::protobuf::io::Printer(&output_stream, '$'); 

    std::map<std::string, std::string> vars;
    // Package string is empty or ends with a dot. It is used to fully qualify
    // method names.
    vars["Package"] = file->package();
    if (!file->package().empty()) {
      vars["Package"].append(".");
    }
    for (int i = 0; i < file->service_count(); ++i) {
      PrintHeaderService(printer, file->service(i), &vars);
      printer->Print("\n");
    }
    delete printer; 
  }
  
  return output;
}

std::string GetHeaderEpilogue(const google::protobuf::FileDescriptor* file) {
  std::string output;
  {
    // Scope the output stream so it closes and finalizes output to the string.
    //auto printer = file->CreatePrinter(&output);
    google::protobuf::io::StringOutputStream output_stream(&output);   
    //google::protobuf::io::Printer printer(&output_stream, '$'); 
    google::protobuf::io::Printer *printer = new google::protobuf::io::Printer(&output_stream, '$'); 

    std::map<std::string, std::string> vars;

    vars["filename"] = file->name();
    vars["filename_identifier"] = FilenameIdentifier(file->name());

    if (!file->package().empty()) {
      std::vector<std::string> parts = lrpc_generator::tokenize(file->package(), "."); //file->package_parts();

      for (auto part = parts.rbegin(); part != parts.rend(); part++) {
        vars["part"] = *part;
        printer->Print(vars, "}  // namespace $part$\n");
      }
      printer->Print(vars, "\n");
    }

    printer->Print(vars, "\n");
    printer->Print(vars, "#endif  // IRPC_$filename_identifier$__INCLUDED\n");

    //printer->Print(file->GetTrailingComments("//").c_str());
    delete printer; 
  }
   
  return output;
}

std::string GetSourcePrologue(const google::protobuf::FileDescriptor* file) {
  std::string output;
  {
    // Scope the output stream so it closes and finalizes output to the string.
    //auto printer = file->CreatePrinter(&output);
    google::protobuf::io::StringOutputStream output_stream(&output);   
    //google::protobuf::io::Printer printer(&output_stream, '$'); 
    google::protobuf::io::Printer *printer = new google::protobuf::io::Printer(&output_stream, '$'); 

    std::map<std::string, std::string> vars;

    vars["filename"] = file->name();
    vars["filename_base"] = lrpc_generator::StripProto(file->name()); //file->filename_without_ext();
    vars["message_header_ext"] = ".pb.h";
    vars["service_header_ext"] = ".lrpc.pb.h";

    printer->Print(vars, "// Generated by the iRPC C++ plugin (based on gRPC - https://github.com/grpc/grpc/tree/master/src/compiler.)\n");
    printer->Print(vars,
                   "// If you make any local change, they will be lost.\n");
    printer->Print(vars, "// source: $filename$\n\n");

    printer->Print(vars, "#include \"$filename_base$$message_header_ext$\"\n");
    printer->Print(vars, "#include \"$filename_base$$service_header_ext$\"\n");
    printer->Print(vars, "\n");
    delete printer; 
  }
   
  return output;
}

std::string GetSourceIncludes(const google::protobuf::FileDescriptor* file) {
  std::string output;
  {
    // Scope the output stream so it closes and finalizes output to the string.
    //auto printer = file->CreatePrinter(&output);
    google::protobuf::io::StringOutputStream output_stream(&output);   
    //google::protobuf::io::Printer printer(&output_stream, '$'); 
    google::protobuf::io::Printer *printer = new google::protobuf::io::Printer(&output_stream, '$'); 

    std::map<std::string, std::string> vars;
    static const char* headers_strs[] = {
        "x86intrin.h",
        "string.h"};
    std::vector<std::string> headers(headers_strs, array_end(headers_strs));
    PrintIncludes(printer, headers, true, ""); 

    if (!file->package().empty()) {
      std::vector<std::string> parts = lrpc_generator::tokenize(file->package(), "."); //file->package_parts();

      for (auto part = parts.begin(); part != parts.end(); part++) {
        vars["part"] = *part;
        printer->Print(vars, "\nnamespace $part$ {\n");
      }
    }

    printer->Print(vars, "\n");
    delete printer; 
  }
   
  return output;
}

void PrintSourceClientMethod(google::protobuf::io::Printer* printer,
                                       const google::protobuf::MethodDescriptor* method,
                                       std::map<std::string, std::string>* vars) {
  auto *req = method->input_type();
  auto *resp = method->output_type();

  (*vars)["Method"] = method->name();
  (*vars)["Request"] = req->name(); //method->input_type_name();
  (*vars)["Response"] = resp->name(); //method->output_type_name();

  std::string temp = method->name(); 
  (*vars)["Method_ID"] = std::to_string(rte_hash_crc(temp.c_str(), temp.length(), 0)); 

  struct {
    std::string prefix;
    std::string start;          // bool literal expressed as string
    std::string method_params;  // extra arguments to method
    std::string create_args;    // extra arguments to creator
  } async_prefixes[] = {{"Async", "true", ", void* tag", ", tag"},
                        {"PrepareAsync", "false", "", ", nullptr"}};
  if (NoStreaming(method)) {

    // Sync API
    printer->Print(*vars,
                   "int32_t $ns$$Service$::Stub::$Method$("
                   "const $Request$& request, $Response$* response) {\n");
    printer->Indent();
    printer->Indent();
    printer->Print(*vars,
                    "Rpc_message $Method$_req;\n"
                    "Rpc_message $Method$_rsp;\n"
                    "lrpc_addr_t to;\n\n"
                    "// create rpc message\n"
                    "$Method$_req.rpc_service_id = $Service_ID$;\n"
                    "$Method$_req.rpc_instance_id = lrpc_endpoint_->geniRPCInstanceId();\n"
                    "$Method$_req.rpc_method_id = $Method_ID$;\n"
                    "$Method$_req.rpc_type = kRpcTypeReq;\n\n"
                    "// protobuf \n"
                    "std::string serialized_str;\n"
                    "request.SerializeToString(&serialized_str);\n"
                    "memcpy(&$Method$_req.rpc_parameters[0], &serialized_str[0], serialized_str.size());\n"
                    "$Method$_req.rpc_parameters_size = serialized_str.size(); // request.ByteSizeLong();\n\n"
                    "// Enqueue an RPC request with a placeholder for the response\n"
                    "Rpc_active_call new_call;\n"
                    "new_call.response = response;\n"
                    "#ifdef LB\n"
                    "remote_iprc_endpont_addr = LB(service_ip);\n"
                    "#endif\n"
                    "to.qword[0] = IRPC_ADDR_UNSPEC; // Unspecified Address\n"
                    "if (lrpc_endpoint_->lrpc_enqueue(&to, &$Method$_req, &new_call) != 1) { \n"
                    "    printf(\"Error: Failed to enqueue an RPC request!\\n\");\n"
                    "    return (-1);\n"
                    "}\n\n"
                    "#ifdef NO_RT // No RT -- try to dequeue the response within the app thread context\n"
                    "lrpc_addr_t from;\n"
                    "if (lrpc_endpoint_->lrpc_dequeue(&from, &$Method$_rsp) != 1) { \n"
                    "    printf(\"Error: Failed to dequeue an RPC request!\\n\");\n"
                    "    return (-1);\n"
                    "}\n"
                    "response->ParseFromString((char *)&$Method$_rsp.rpc_parameters[0]);\n"
                    "#else // With RT -- let a worker thread dequeue the response and signal the resp_ready_flag\n"
                    "new_call.resp_ready_flag.wait(false);\n"
                    "#endif\n\n"
                    "// SYNC API: Received the response, so return from the completed call\n"
                    "return 0;\n");
    printer->Outdent();
    printer->Outdent();  
    printer->Print("}\n\n");                    

    // Async API
    printer->Print(*vars,
                   "int32_t $ns$$Service$::Stub::Async$Method$("
                   "const $Request$ &request, $Response$* response, "
                   "Rpc_active_call *new_call) {\n");
    printer->Indent();
    printer->Indent();
    printer->Print(*vars,
                    "Rpc_message $Method$_req;\n"
                    "Rpc_message $Method$_rsp;\n"
                    "lrpc_addr_t to;\n\n"
                    "// create rpc message\n"
                    "$Method$_req.rpc_service_id = $Service_ID$;\n"
                    "$Method$_req.rpc_instance_id = lrpc_endpoint_->geniRPCInstanceId();\n"
                    "$Method$_req.rpc_method_id = $Method_ID$;\n"
                    "$Method$_req.rpc_type = kRpcTypeReq;\n\n"
                    "// protobuf \n"
                    "std::string serialized_str;\n"
                    "request.SerializeToString(&serialized_str);\n"
                    "memcpy(&$Method$_req.rpc_parameters[0], &serialized_str[0], serialized_str.size());\n"
                    "$Method$_req.rpc_parameters_size = serialized_str.size(); // request.ByteSizeLong();\n\n"
                    "// Enqueue an RPC request with a placeholder for the response\n"
                    "new_call->resp_ready_flag.store(false);\n"
                    "new_call->response = response;\n"
                    "#ifdef LB\n"
                    "remote_iprc_endpont_addr = LB(service_ip);\n"
                    "#endif\n"
                    "to.qword[0] = IRPC_ADDR_UNSPEC; // Unspecified Address\n"
                    "if (lrpc_endpoint_->lrpc_enqueue(&to, &$Method$_req, new_call) != 1) { \n"
                    "    printf(\"Error: Failed to enqueue an RPC request!\\n\");\n"
                    "    return (-1);\n"
                    "}\n\n"
                    "// ASYNC API: Don't wait for the response, return from the call, application does the rest\n"
                    "return 0;\n");
    printer->Outdent();
    printer->Outdent();  
    printer->Print("}\n\n");
  } else if (ClientOnlyStreaming(method)) {
  } else if (ServerOnlyStreaming(method)) {
  } else if (BidiStreaming(method)) {    
  }
}

void PrintSourceServerMethod(google::protobuf::io::Printer* printer,
                                       const google::protobuf::MethodDescriptor* method,
                                       std::map<std::string, std::string>* vars) {
  auto *req = method->input_type();
  auto *resp = method->output_type();

  (*vars)["Method"] = method->name();
  (*vars)["Request"] = req->name(); //method->input_type_name();
  (*vars)["Response"] = resp->name(); //method->output_type_name();

  if (NoStreaming(method)) {
    printer->Print(*vars,
                   "int32_t $ns$$Service$::Service::$Method$("
                   "const $Request$* request, $Response$* response) {\n");
    printer->Print("  (void) request;\n");
    printer->Print("  (void) response;\n");
    printer->Print(
        "  return 0;\n");
    printer->Print("}\n\n");
  } else if (ClientOnlyStreaming(method)) {
  } else if (ServerOnlyStreaming(method)) {
  } else if (BidiStreaming(method)) {
  }
}

void PrintSourceService(google::protobuf::io::Printer* printer,
                                       const google::protobuf::ServiceDescriptor *service,
                                       std::map<std::string, std::string>* vars) {
  (*vars)["Service"] = service->name();

  if (service->method_count() > 0) {
    printer->Print(*vars,
                   "static const char* $prefix$$Service$_method_names[] = {\n");
    for (int i = 0; i < service->method_count(); ++i) {
      (*vars)["Method"] = service->method(i)->name();
      printer->Print(*vars, "  \"/$Package$$Service$/$Method$\",\n");
    }
    printer->Print(*vars, "};\n\n");
  }

  printer->Print("///////////////////////////////////////////////////\n"); 
  printer->Print(*vars,
                 "$ns$$Service$::Stub::Stub(Lrpc_endpoint *lrpc_endpoint, const char *uri)\n");
  printer->Indent();
  printer->Print(": lrpc_endpoint_(lrpc_endpoint)");
  printer->Print("{}\n\n");
  printer->Outdent();

  for (int i = 0; i < service->method_count(); ++i) {
    (*vars)["Idx"] = as_string(i);
    std::string temp = service->name();
    (*vars)["Service_ID"] = std::to_string(rte_hash_crc(temp.c_str(), temp.length(), 0)); 
    PrintSourceClientMethod(printer, service->method(i), vars);
  }

  ///////////////////////////////////////////
  // geneating service skeleton
  printer->Print("\n////////////////////////////////////////////////////////////\n"); 
  printer->Print(*vars, "$ns$$Service$::Service::Service(Lrpc_endpoint *lrpc_endpoint) : lrpc_endpoint_(lrpc_endpoint) {\n");
  printer->Indent();
  for (int i = 0; i < service->method_count(); ++i) {
    auto method = service->method(i);
    (*vars)["Idx"] = as_string(i);

    auto *req = method->input_type();
    auto *resp = method->output_type();

    (*vars)["Method"] = method->name();
    (*vars)["Request"] = req->name(); //method->input_type_name();
    (*vars)["Response"] = resp->name(); //method->output_type_name();

    if (NoStreaming(method)) {
      printer->Print(
          *vars,
          "methods_.emplace_back(new ::lrpc::RpcServiceMethod(\n"
          "    new ::lrpc::RpcMethodHandler< $ns$$Service$::Service, "
          "$Request$, $Response$, ::google::protobuf::MessageLite, "
          "::google::protobuf::MessageLite>(\n"
          "        []($ns$$Service$::Service* service,\n"
          "           const $Request$* req,\n"
          "           $Response$* resp) {\n"
          "             return service->$Method$(req, resp);\n"
          "           }, this))); \n");
    } else if (ClientOnlyStreaming(method)) {
    } else if (ServerOnlyStreaming(method)) {
    } else if (BidiStreaming(method)) {
    }
  }
  
  printer->Print(*vars, "\nlrpc_endpoint_->register_rpc_service(this);\n"); 
  printer->Outdent();
  printer->Print(*vars, "}\n\n");

  // destructor 
  printer->Print(*vars,
                 "$ns$$Service$::Service::~Service() {\n"
                 "}\n\n");

  // generate method_for_request_id() per service_id
  printer->Print(*vars, "RpcServiceMethod * \n"); 
  printer->Print(*vars, "$ns$$Service$::Service::method_for_request_id(uint32_t idx) {\n");
  printer->Indent();
  printer->Print(*vars, "switch(idx){\n"); 
  printer->Indent();
  int j = 0;  
  for (int i = 0; i < service->method_count(); ++i) {
    auto method = service->method(i);

    // current only support unary calls
    if (NoStreaming(method))  {
      (*vars)["Idx"] = as_string(j);

      std::string temp = service->name();
      (*vars)["Service_ID"] = std::to_string(rte_hash_crc(temp.c_str(), temp.length(), 0)); 
      temp = method->name(); 
      (*vars)["Method_ID"] = std::to_string(rte_hash_crc(temp.c_str(), temp.length(), 0)); 

      printer->Print(*vars, "case ($Service_ID$ ^ $Method_ID$): return methods_[$Idx$].get();\n");

      j++; 
    }
  }
  printer->Print(*vars, "default: return nullptr;\n"); 
  printer->Outdent(); 
  printer->Print(*vars, "}\n");
  printer->Outdent();
  printer->Print(*vars, "}\n\n");
  
  // generate empty RPC functions
  for (int i = 0; i < service->method_count(); ++i) {
    (*vars)["Idx"] = as_string(i);
    PrintSourceServerMethod(printer, service->method(i), vars);
  }
}
                                       
std::string GetSourceServices(const google::protobuf::FileDescriptor* file) {
  std::string output;
  {
    // Scope the output stream so it closes and finalizes output to the string.
    //auto printer = file->CreatePrinter(&output);
    google::protobuf::io::StringOutputStream output_stream(&output);   
    //google::protobuf::io::Printer printer(&output_stream, '$'); 
    google::protobuf::io::Printer *printer = new google::protobuf::io::Printer(&output_stream, '$'); 

    std::map<std::string, std::string> vars;
    // Package string is empty or ends with a dot. It is used to fully qualify
    // method names.
    vars["Package"] = file->package();
    if (!file->package().empty()) {
      vars["Package"].append(".");
    }

    vars["ns"] = "";
    vars["prefix"] = "";

    for (int i = 0; i < file->service_count(); ++i) {
      PrintSourceService(printer, file->service(i), &vars);
      printer->Print("\n");
    }
    delete printer; 
  }
   
  return output;
}

std::string GetSourceEpilogue(const google::protobuf::FileDescriptor* file) {
  std::string temp;

  if (!file->package().empty()) {
    std::vector<std::string> parts = lrpc_generator::tokenize(file->package(), "."); //file->package_parts();

    for (auto part = parts.begin(); part != parts.end(); part++) {
      temp.append("}  // namespace ");
      temp.append(*part);
      temp.append("\n");
    }
    temp.append("\n");
  }

  return temp;
}

}  // namespace lrpc_cpp_generator
